//
//  TTDatabaseObject.m
//  Tint
//
//  Created by Kevin Wu on 5/30/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTDatabaseObject.h"
#import <objc/runtime.h>
#import "TTDatabase.h"


@implementation TTDatabaseObject


#pragma mark - Accessors

@synthesize pk = _pk;


#pragma mark - Memory

- (id)init
{
    self = [super init];
    if ( self ) {
        _pk = 0;
    }
    return self;
}

- (void)dealloc
{
    _pk = 0;
    [super dealloc];
}


#pragma mark - Public

static TTDatabase *currentDatabase = nil;

+ (void)setCurrentDatabase:(TTDatabase *)database
{
    TTRetain(database, currentDatabase);
}

+ (TTDatabase *)currentDatabase
{
    return currentDatabase;
}

- (TTDatabase *)currentDatabase
{
    return currentDatabase;
}


+ (BOOL)createTable
{
    TTDASSERT([self currentDatabase]);
    
    if ( [[self currentDatabase] hasTable:[self classString]] ) {
        return YES;
    }
    
    
    NSMutableArray *components = [[NSMutableArray alloc] init];
    
    NSArray *propertyNames = [[self class] newPropertyNames];
    NSDictionary *propertyAttributes = [[self class] newPropertyAttributes];
    
    for ( int i=0; i<[propertyNames count]; ++i ) {
        NSString *name = [propertyNames objectAtIndex:i];
        NSString *attribute = [propertyAttributes objectForKey:name];
        
        if ( [attribute hasPrefix:@"Tc"] ) { // BOOL: Tc
            NSString *component = [[NSString alloc] initWithFormat:@"%@ INTEGER", name];
            [components addObject:component];
            [component release];
        } else if ( [attribute hasPrefix:@"Ti"] ) { // int: Ti
            NSString *component = [[NSString alloc] initWithFormat:@"%@ INTEGER", name];
            [components addObject:component];
            [component release];
        } else if ( [attribute hasPrefix:@"Tq"] ) { // long long: Tq
            NSString *component = [[NSString alloc] initWithFormat:@"%@ INTEGER", name];
            [components addObject:component];
            [component release];
        } else if ( [attribute hasPrefix:@"Td"] ) { // double: Td
            NSString *component = [[NSString alloc] initWithFormat:@"%@ REAL", name];
            [components addObject:component];
            [component release];
        } else if ( [attribute hasPrefix:@"T@\"NSString\""] ) { // NSString: T@"NSString"
            NSString *component = [[NSString alloc] initWithFormat:@"%@ TEXT", name];
            [components addObject:component];
            [component release];
        } else if ( [attribute hasPrefix:@"T@\"NSDate\""] ) { // NSDate: T@"NSDate"
            NSString *component = [[NSString alloc] initWithFormat:@"%@ TEXT", name];
            [components addObject:component];
            [component release];
        }
        
    }
    
    TTRelease(propertyNames);
    TTRelease(propertyAttributes);
    
    
    BOOL status = NO;
    
    if ( [components count] > 0 ) {
        
        [components insertObject:@"pk INTEGER PRIMARY KEY" atIndex:0];
        
        NSString *sql = [[NSString alloc] initWithFormat:@"CREATE TABLE %@(%@);", 
                         [self classString], 
                         [components componentsJoinedByString:@", "]];
        status = [[self currentDatabase] executeUpdateString:sql];
        TTRelease(sql);
    }
    
    TTRelease(components);
    
    return status;
}

+ (BOOL)deleteTable
{
    NSString *sql = [[NSString alloc] initWithFormat:@"DROP TABLE %@;", [self classString]];
    BOOL status = [[self currentDatabase] executeUpdateString:sql];
    TTRelease(sql);
    return status;
}



- (BOOL)save
{
    NSArray *propertyNames = [[self class] newPropertyNames];
    BOOL status = [self saveFields:propertyNames exceptFields:nil];
    TTRelease(propertyNames);
    return status;
}

- (BOOL)save:(NSString *)fieldsString
{
    return [self saveFields:[fieldsString componentsSeparatedByString:@","] exceptFields:nil];
}

- (BOOL)saveExcept:(NSString *)exceptFieldsString
{
    NSArray *propertyNames = [[self class] newPropertyNames];
    NSArray *exceptFields = [exceptFieldsString componentsSeparatedByString:@","];
    BOOL status = [self saveFields:propertyNames exceptFields:exceptFields];
    TTRelease(propertyNames);
    return status;
}

- (BOOL)saveFields:(NSArray *)fields exceptFields:(NSArray *)exceptFields
{
    TTDASSERT([self currentDatabase]);
    
    NSMutableArray *tmpFields = [[NSMutableArray alloc] init];
    
    for ( NSString *field in fields ) {
        NSString *trimmed = [field newStringByTrimmingWhitespaceAndNewline];
        if ( [trimmed length] > 0 ) {
            [tmpFields addUnequalObjectIfNotNil:trimmed];
        }
        TTRelease(trimmed);
    }
    
    
    NSMutableArray *tmpExceptFields = [[NSMutableArray alloc] init];
    
    for ( NSString *field in exceptFields ) {
        NSString *trimmed = [field newStringByTrimmingWhitespaceAndNewline];
        if ( [trimmed length] > 0 ) {
            [tmpExceptFields addUnequalObjectIfNotNil:trimmed];
        }
        TTRelease(trimmed);
    }
    
    
    NSMutableArray *finalFields = [[NSMutableArray alloc] init];
    
    for ( NSString *field in tmpFields ) {
        if ( ![tmpExceptFields hasObjectEqualTo:field] ) {
            [finalFields addUnequalObjectIfNotNil:field];
        }
    }
    
    TTRelease(tmpFields);
    TTRelease(tmpExceptFields);
    
    if ( [finalFields count] < 1 ) {
        TTRelease(finalFields);
        return NO;
    }
    
    
    NSMutableArray *components = [[NSMutableArray alloc] init];
    NSMutableArray *placeholders = [[NSMutableArray alloc] init];
    NSMutableArray *values = [[NSMutableArray alloc] init];
    
    NSArray *propertyNames = [[self class] newPropertyNames];
    NSDictionary *propertyAttributes = [[self class] newPropertyAttributes];
    
    for ( int i=0; i<[propertyNames count]; ++i ) {
        NSString *name = [propertyNames objectAtIndex:i];
        NSString *attribute = [propertyAttributes objectForKey:name];
        
        if ( [finalFields hasObjectEqualTo:name] ) {
            
            if ( [attribute hasPrefix:@"Tc"] ) { // BOOL: Tc
                [components addObject:name];
                [placeholders addObject:@"?"];
                id value = [self valueForKey:name];
                if ( value == nil ) value = [NSNull null];
                [values addObject:value];
            } else if ( [attribute hasPrefix:@"Ti"] ) { // int: Ti
                [components addObject:name];
                [placeholders addObject:@"?"];
                id value = [self valueForKey:name];
                if ( value == nil ) value = [NSNull null];
                [values addObject:value];
            } else if ( [attribute hasPrefix:@"Tq"] ) { // long long: Tq
                [components addObject:name];
                [placeholders addObject:@"?"];
                id value = [self valueForKey:name];
                if ( value == nil ) value = [NSNull null];
                [values addObject:value];
            } else if ( [attribute hasPrefix:@"Td"] ) { // double: Td
                [components addObject:name];
                [placeholders addObject:@"?"];
                id value = [self valueForKey:name];
                if ( value == nil ) value = [NSNull null];
                [values addObject:value];
            } else if ( [attribute hasPrefix:@"T@\"NSString\""] ) { // NSString: T@"NSString"
                [components addObject:name];
                [placeholders addObject:@"?"];
                id value = [self valueForKey:name];
                if ( value == nil ) value = [NSNull null];
                [values addObject:value];
            } else if ( [attribute hasPrefix:@"T@\"NSDate\""] ) { // NSDate: T@"NSDate"
                [components addObject:name];
                [placeholders addObject:@"?"];
                id value = [self valueForKey:name];
                if ( value == nil ) value = [NSNull null];
                [values addObject:value];
            }
            
        }
        
    }
    
    TTRelease(propertyNames);
    TTRelease(propertyAttributes);
    
    TTRelease(finalFields);
    
    
    
    BOOL status = NO;
    
    if (([components count] > 0) && 
        ([placeholders count] > 0) && 
        ([values count] > 0) && 
        (([components count] == [placeholders count]) && ([placeholders count] == [values count])))
    {
        
        if ( [self isExist] ) {
            
            NSMutableArray *newComponents = [[NSMutableArray alloc] init];
            for ( int i=0; i<[components count]; ++i ) {
                NSString *component = [components objectAtIndex:i];
                NSString *newComponent = [[NSString alloc] initWithFormat:@"%@=?", component];
                [newComponents addObject:newComponent];
                TTRelease(newComponent);
            }
            
            NSNumber *pk = [[NSNumber alloc] initWithInt:_pk];
            [values addObject:pk];
            TTRelease(pk);
            
            NSString *sql = [[NSString alloc] initWithFormat:@"UPDATE %@ SET %@ WHERE pk=?;", 
                             [self classString], 
                             [newComponents componentsJoinedByString:@", "]];
            status = [[self currentDatabase] executeUpdate:sql parameters:values];
            TTRelease(sql);
            TTRelease(newComponents);
            
        } else {
            
            if ( _pk > 0 ) {
                [components addObject:@"pk"];
                [placeholders addObject:@"?"];
                NSNumber *pk = [[NSNumber alloc] initWithInt:_pk];
                [values addObject:pk];
                TTRelease(pk);
            }
            
            NSString *sql = [[NSString alloc] initWithFormat:@"INSERT INTO %@(%@) VALUES(%@);", 
                             [self classString], 
                             [components componentsJoinedByString:@", "], 
                             [placeholders componentsJoinedByString:@", "]];
            sqlite3_int64 rowid = [[self currentDatabase] executeInsert:sql parameters:values];
            TTRelease(sql);
            
            if ( rowid > 0 ) {
                self.pk = rowid;
                status = YES;
            }
            
        }
        
        
    }
    
    TTRelease(components);
    TTRelease(placeholders);
    TTRelease(values);
    
    return status;
    
}


- (BOOL)destroy
{
    TTDASSERT([self currentDatabase]);
    
    NSString *sql = [[NSString alloc] initWithFormat:@"DELETE FROM %@ WHERE pk=?;", [self classString]];
    NSNumber *pk = [[NSNumber alloc] initWithInt:_pk];
    BOOL status = [[self currentDatabase] executeUpdate:sql, pk, nil];
    TTRelease(pk);
    TTRelease(sql);
    return status;
}


- (BOOL)isExist
{
    NSNumber *pk = [[NSNumber alloc] initWithInt:_pk];
    NSUInteger count = [[self class] countWhere:@"pk=?", pk, nil];
    TTRelease(pk);
    return ( count > 0 );
}


- (void)reload
{
    TTDASSERT([self currentDatabase]);
    
    NSString *sql = [[NSString alloc] initWithFormat:@"SELECT * FROM %@ WHERE pk=?;", [self classString]];
    NSNumber *pk = [[NSNumber alloc] initWithInt:_pk];
    TTDatabaseResult *result = [[self currentDatabase] newResultByExecutingQuery:sql, pk, nil];
    TTRelease(pk);
    TTRelease(sql);
    TTDatabaseRow *row = [result rowAtIndex:0];
    [self loadValueFromDatabaseRow:row];
    TTRelease(result);
}


+ (NSUInteger)count
{
    return [self countWhere:nil];
}

+ (NSUInteger)countWhere:(NSString *)where, ...
{
    TTDASSERT([self currentDatabase]);
    
    NSMutableString *sql = [[NSMutableString alloc] init];
    [sql appendFormat:@"SELECT COUNT(*) AS count FROM %@", [self classString]];
    if ( where ) [sql appendFormat:@" WHERE %@", where];
    [sql appendString:@";"];
    
    NSMutableArray *parameters = nil;
    TTValistToArray(where, parameters);
    
    TTDatabaseResult *result = [[self currentDatabase] newResultByExecutingQuery:sql parameters:parameters];
    TTRelease(sql);
    TTRelease(parameters);
    
    TTDatabaseRow *row = [result rowAtIndex:0];
    NSUInteger count = [row intForColumnAtIndex:0];
    TTRelease(result);
    
    return count;
}

+ (BOOL)deleteAllObjects
{
    return [self deleteObjectsWhere:nil];
}

+ (BOOL)deleteObjectsWhere:(NSString *)where, ...
{
    TTDASSERT([self currentDatabase]);
    
    NSMutableString *sql = [[NSMutableString alloc] init];
    [sql appendFormat:@"DELETE FROM %@", [self classString]];
    if ( where ) [sql appendFormat:@" WHERE %@", where];
    [sql appendString:@";"];
    
    NSMutableArray *parameters = nil;
    TTValistToArray(where, parameters);
    
    BOOL status = [[self currentDatabase] executeUpdate:sql parameters:parameters];
    TTRelease(sql);
    TTRelease(parameters);
    
    return status;
}


+ (BOOL)updateAllObjectsSet:(NSString *)set
{
    return [self updateObjectsSet:set where:nil];
}

+ (BOOL)updateObjectsSet:(NSString *)set where:(NSString *)where, ...
{
    TTDASSERT([self currentDatabase]);
    
    if ( set ) {
        NSMutableString *sql = [[NSMutableString alloc] init];
        [sql appendFormat:@"UPDATE %@ SET %@", [self classString], set];
        if ( where ) [sql appendFormat:@" WHERE %@", where];
        [sql appendString:@";"];
        
        NSMutableArray *parameters = nil;
        TTValistToArray(where, parameters);
        
        BOOL status = [[self currentDatabase] executeUpdate:sql parameters:parameters];
        TTRelease(sql);
        TTRelease(parameters);
        
        return status;
    }
    return NO;
}




+ (id)copyFirstObject
{
    return [self copyFirstObjectOrderBy:nil where:nil parameters:nil];
}

+ (id)copyFirstObjectOrderBy:(NSString *)orderBy
{
    return [self copyFirstObjectOrderBy:orderBy where:nil parameters:nil];
}

+ (id)copyFirstObjectWhere:(NSString *)where, ...
{
    NSMutableArray *parameters = nil;
    TTValistToArray(where, parameters);
    id object = [self copyFirstObjectOrderBy:nil where:where parameters:parameters];
    TTRelease(parameters);
    return object;
}

+ (id)copyFirstObjectOrderBy:(NSString *)orderBy where:(NSString *)where, ...
{
    NSMutableArray *parameters = nil;
    TTValistToArray(where, parameters);
    id object = [self copyFirstObjectOrderBy:orderBy where:where parameters:parameters];
    TTRelease(parameters);
    return object;
}

+ (id)copyFirstObjectOrderBy:(NSString *)orderBy where:(NSString *)where parameters:(NSArray *)parameters
{
    TTDASSERT([self currentDatabase]);
    
    NSMutableString *sql = [[NSMutableString alloc] init];
    [sql appendFormat:@"SELECT * FROM %@", [self classString]];
    if ( where ) [sql appendFormat:@" WHERE %@", where];
    if ( orderBy ) [sql appendFormat:@" ORDER BY %@", orderBy];
    [sql appendString:@";"];
    
    TTDatabaseResult *result = [[self currentDatabase] newResultByExecutingQuery:sql parameters:parameters];
    TTRelease(sql);
    
    id object = nil;
    
    if ( [result count] > 0 ) {
        
        object = [[self alloc] init];
        
        TTDatabaseRow *row = [result rowAtIndex:0];
        [object loadValueFromDatabaseRow:row];
        
        NSNumber *pk = [[NSNumber alloc] initWithInt:[row intForColumn:@"pk"]];
        [object setValue:pk forKey:@"pk"];
        TTRelease(pk);
    }
    
    TTRelease(result);
    
    return object;
}



+ (NSArray *)copyAllObjects
{
    return [self copyObjectsOrderBy:nil where:nil parameters:nil];
}

+ (NSArray *)copyObjectsOrderBy:(NSString *)orderBy
{
    return [self copyObjectsOrderBy:orderBy where:nil parameters:nil];
}

+ (NSArray *)copyObjectsWhere:(NSString *)where, ...
{
    NSMutableArray *parameters = nil;
    TTValistToArray(where, parameters);
    NSArray *objects = [self copyObjectsOrderBy:nil where:where parameters:parameters];
    TTRelease(parameters);
    return objects;
}

+ (NSArray *)copyObjectsOrderBy:(NSString *)orderBy where:(NSString *)where, ...
{
    NSMutableArray *parameters = nil;
    TTValistToArray(where, parameters);
    NSArray *objects = [self copyObjectsOrderBy:orderBy where:where parameters:parameters];
    TTRelease(parameters);
    return objects;
}

+ (NSArray *)copyObjectsOrderBy:(NSString *)orderBy where:(NSString *)where parameters:(NSArray *)parameters
{
    TTDASSERT([self currentDatabase]);
    
    NSMutableString *sql = [[NSMutableString alloc] init];
    [sql appendFormat:@"SELECT * FROM %@", [self classString]];
    if ( where ) [sql appendFormat:@" WHERE %@", where];
    if ( orderBy ) [sql appendFormat:@" ORDER BY %@", orderBy];
    [sql appendString:@";"];
    
    TTDatabaseResult *result = [[self currentDatabase] newResultByExecutingQuery:sql parameters:parameters];
    TTRelease(sql);
    
    NSMutableArray *objects = nil;
    
    if ( [result count] > 0 ) {
        
        objects = [[NSMutableArray alloc] init];
        
        for ( int i=0; i<[result count]; ++i ) {
            
            id object = [[self alloc] init];
            
            TTDatabaseRow *row = [result rowAtIndex:i];
            [object loadValueFromDatabaseRow:row];
            
            NSNumber *pk = [[NSNumber alloc] initWithInt:[row intForColumn:@"pk"]];
            [object setValue:pk forKey:@"pk"];
            TTRelease(pk);
            
            [objects addObject:object];
            [object release];
        }
        
    }
    
    TTRelease(result);
    
    return objects;
}


@end
